'use strict';

var FS     = require('fs-extra');
var Path   = require('path');
var Assert = require('assert');

/** 
* @fileOverview jfile
* @author hilldev
* @version 0.1
*/ 

//============================================================
/**
* @class 
* @constructor JFile
* @description An abstract representation of file and directory pathnames.
* @example new JFile("parent", "child1", "child2"); 
* @since version 0.1
* @param {String|JFile} parent parent The parent pathname string 
* @param {String} optional, childs The child pathname string
* 
*/
function /*Class*/ PGFile( paths) {
	this._path = undefined;
	if(arguments.length === 0){
		arguments[0] = '.';
	}else if (arguments[0] instanceof PGFile) {
		arguments[0] = arguments[0].getPath();
		for (var i = 0; i < arguments.length; ++i) {
			var arg = arguments[i];
			Assert.ok(!!arg,'no such file or directory:' + arg);
			Assert.ok((typeof arg === 'string') || (arg instanceof String), 'path must be a string');
		}
	}
	this._path = Path.join.apply(Path, arguments);
}
/**
 * Function append(path ...)
 *
 * @path string
 * @return PGFile
 */
PGFile.prototype.append = function( /*String*/ paths) {
	Assert.ok(arguments.length > 0, 'invalid arguments');
	for (var i = 0; i < arguments.length; ++i) {
		var arg = arguments[i];
		Assert.ok(!!arg,'no such file or directory:' + arg);
		Assert.ok((typeof arg === 'string') || (arg instanceof String), 'path must be a string');
	}
	return new PGFile(this._path, Path.join.apply(Path, arguments));
}

PGFile.prototype.getParentFile = function() {
	return new PGFile(this.getParent());
};

PGFile.prototype.getParent = function() {
	var absPath = this.getAbsolutePath();
	var parPath = Path.dirname(absPath);
	if (absPath == parPath) {
		throw new Error('it\'s already the root directory');
	}
	return parPath;
};

PGFile.prototype.getName = function() {
	return Path.basename(this._path);
};

PGFile.prototype.getPath = function() {
	return String(this._path);
};

PGFile.prototype.getAbsolutePath = function() {
	return Path.resolve(this._path);
};

///**
// * @param <boolean> isHidden
// *            default: true
// *            apply attribute hidden to all folder and sub files.
// * @return <boolean> true if success; false otherwise
// */
//PGFile.prototype.setHidden = function( /*boolean*/ isHidden) {
//	var attributes = {
//		// IS_ARCHIVED: true, //true means yes
//		IS_HIDDEN: isHidden//, //false means no
//		//IS_NOT_CONTENT_INDEXED:true,//remove this attribute if you don't want to change it
//		// IS_OFFLINE: false,
//		// IS_READ_ONLY: true,
//		// IS_SYSTEM: false,
//		// IS_TEMPORARY: true
//	};
//	// return FSWin.setAttributesSync(this._path, attributes);
//	return false;
//};

/**
 * Tests whether the file or directory denoted by this abstract pathname exists.
 *
 * return <boolean>: true if and only if the file or directory denoted by this abstract pathname exists; false otherwise
 * Throws:
 */
PGFile.prototype.exists = function() {
	return FS.existsSync(this._path);
};

/**
 * mkdirs(callback(err))
 * Creates the directory named by this abstract pathname, including any
 * necessary but nonexistent parent directories. Note that if this operation
 * fails it may have succeeded in creating some of the necessary parent
 * directories.
 */
PGFile.prototype.mkdirs = function( /*Function*/ callback) {
	Assert.ok(arguments.length == 1, 'invalid arguments');
	return FS.mkdirs(this._path, callback);
};
/**
 * Creates the directory named by this abstract pathname, including any
 * necessary but nonexistent parent directories. Note that if this operation
 * fails it may have succeeded in creating some of the necessary parent
 * directories.
 *
 * @return <boolean> true if and only if the directory was created, along with all
 *         necessary parent directories; false otherwise
 * @throws Error
 */
PGFile.prototype.mkdirsSync = function() {
	return FS.mkdirsSync(this._path);
};
/**
 * ensureDir(callback(err))
 * Ensures that the directory exists.
 * If the directory structure does not exist, it is created.
 *
 */
PGFile.prototype.ensureDir = function( /*Function*/ callback) {
	Assert.ok(arguments.length == 1, 'invalid arguments');
	return FS.ensureDir(this._path, callback);
};

/**
 * Creates the directory named by this abstract pathname, including any
 * necessary but nonexistent parent directories. Note that if this operation
 * fails it may have succeeded in creating some of the necessary parent
 * directories.
 *
 * @return <boolean> true if and only if the directory was created, along with all
 *         necessary parent directories; false otherwise
 */
PGFile.prototype.ensureDirSync = function() {
	return FS.ensureDirSync(this._path);
};
/**
 * ensureFile(callback(err))
 *
 * Ensures that the file exists.
 * If the file that is requested to be created is in directories that do not exist,
 * these directories are created. If the file already exists, it is NOT MODIFIED.
 * @param callback <Function(Error)>
 */
PGFile.prototype.ensureFile = function( /*Function*/ callback) {
	Assert.ok(arguments.length == 1, 'invalid arguments');
	return FS.ensureFile(this._path, callback);
};

PGFile.prototype.ensureFileSync = function() {
	return FS.ensureFileSync(this._path);
};

/**
 * @param {Function} callback(err, Array<pathname>)
 */
PGFile.prototype.list = function(callback){
	Assert.ok(arguments.length == 1, 'invalid arguments');
	if (!this.isDirectory()) {
		callback(null, null);
		return;
	}
	var parent = this._path;
	FS.readdir(this._path, function(err, filenameArray){
		if(err){
			callback(err);
			return;
		}
		callback(null, filenameArray);
	});
}

	
/**
 * Finds files within a given directory (and optionally its subdirectories)
 * which match an array of extensions.
 *
 * @return <Array<String>> an array of sub file or directory.
 *         if return null, the pathname is not a directory.
 *
 */
PGFile.prototype.listSync = function() {
	if (!this.isDirectory()) {
		return null;
	}
	var filenameArr = FS.readdirSync(this._path);
	var pathArr = [];
	var parent = this._path;
	filenameArr.forEach(function(filename) {
		pathArr.push(Path.join(parent, filename));
	});
	return pathArr;
};
/**
 * Finds files' path within a given directory (and optionally its subdirectories)
 * which match an array of extensions.
 *
 * @param {Function} callback(err, Array<PGFile>) an array of sub file or directory.
 *         if return null, the pathname is not a directory.
 *
 */
PGFile.prototype.listFiles = function(callback) {
	Assert.ok(arguments.length == 1, 'invalid arguments');
	this.list(function(err, filenameArray){
		if(err){
			callback(err);
			return;
		}
		var length = filenameArray.length;
		var fileArray = new Array(length);
		var parent = this._path;
		for (var i = 0; i < length; ++i) {
			fileArray[i] = new PGFile(Path.join(parent, filenameArray[i]));
		}
		callback(null, fileArray);
	});
}

	
/**
 * Finds files' path within a given directory (and optionally its subdirectories)
 * which match an array of extensions.
 *
 * @return <Array<PGFile>> an array of sub file or directory.
 *         if return null, the pathname is not a directory.
 *
 */
PGFile.prototype.listFilesSync = function() {
	var filenameArray = this.listSync();
	if (!filenameArray) {
		return null;
	} 
	var length = filenameArray.length;
	var fileArray = new Array(length);
	var parent = this._path;
	for (var i = 0; i < length; ++i) {
		fileArray[i] =  new PGFile(Path.join(parent, filenameArray[i]));
	}
	return fileArray;
};
/**
 *
 * @return <boolean> true if the file is Directory; false othrewise
 *
 */
PGFile.prototype.isDirectory = function() {
	try{
		return FS.statSync(this._path).isDirectory();
	}catch(err){
		return false;
	}
};
/**
 * @param callback <Function(Error)>
 * @param recursive {Boolean} 
 *        Is true, delete sub subfolders and files.
 */ 
PGFile.prototype.deleteFile = function(callback, recursive){
	Assert.ok(arguments.length == 1, 'invalid arguments');
	if(!!recursive){
		FS.remove(this._path, callback);
	}else{
		FS.unlink(this._path, callback);
	}
};

PGFile.prototype.deleteFileSync = function(recursive){
	if(!!recursive){
		FS.removeSync(this._path);
	}else{
		FS.unlinkSync(this._path);
	}
};

/**
 *
 * @return <boolean> true if the file is File; false othrewise
 *
 */
PGFile.prototype.isFile = function() {
	try{
		return FS.statSync(this._path).isFile();
	}catch(err){
		return false;
	}
};

/**
 * Returns the length of the file denoted by this abstract pathname.
 * The return value is unspecified if this pathname denotes a directory.
 *
 * @return <int> the file size
 * @ Throws
 */
PGFile.prototype.length = function() {
	try{
		return FS.statSync(this._path).size;
	}catch(err){
		return 0;
	}
};

PGFile.prototype.valueOf = function() {
	return String(this.getPath());
};

PGFile.prototype.toString = function() {
	return String(this.getPath());
};

/**
 * function([options])
 *
 * @param option Object
 *        encoding String | Null default = null
 *        flag String default = 'r'
 *
 * @param callback(err, data)
 *
 * If no encoding is specified, then the raw buffer is returned.
 *
 */
PGFile.prototype.readFile = function( /*Object*/ option, /*err, data*/ callback) {
	Assert.ok(arguments.length == 1 || arguments.length == 2, 'invalid arguments');
	if (typeof option === 'function') {
		callback = option;
		option = null;
	}
	var self = this;
	FS.readFile(self._path, option, function(err, data) {
		callback.call(self, err, data);
	});
};

/**
 * function([options])
 * The synchronous version
 *
 * @param option Object
 *       encoding String | Null default = null
 *       flag String default = 'r'
 *
 * @return String|Buffer
 *
 * If no encoding is specified, then the raw buffer is returned.
 *
 * @throws Eroor
 *
 */
PGFile.prototype.readFileSync = function( /*Object*/ option) {
	return FS.readFileSync(this._path, option);
}

/**
 * function(data, [options], callback(err))
 *
 * @param data String | Buffer
 *
 * @param option Object
 *      encoding String | Null default = 'utf8'
 *      mode Number default = 438 (aka 0666 in Octal)
 *      flag String default = 'w'
 *
 * @param callback(err)
 *
 *The encoding option is ignored if data is a buffer. It defaults to 'utf8'.
 *
 */
PGFile.prototype.writeFile = function( /*String|Buffer*/ data, /*Object*/ option, /*err*/ callback) {
	Assert.ok(arguments.length == 2 || arguments.length == 3, 'invalid arguments');
	if (arguments.length < 2) {
		throw new Error('invalid arguments');
	}
	if (typeof option == "function") {
		callback = option;
		option = {
			encoding: "utf8"
		};
	}
	var self = this;
	FS.writeFile(self._path, data, option, function(err) {
		callback.call(self, err);
	});
};

/**
 * function(data, [options])
 * The synchronous version
 *
 * @param data String | Buffer
 *
 * @param option Object
 *      encoding String | Null default = 'utf8'
 *      mode Number default = 438 (aka 0666 in Octal)
 *      flag String default = 'w'
 *
 *The encoding option is ignored if data is a buffer. It defaults to 'utf8'.
 *
 * @throws Eroor
 *
 */
PGFile.prototype.writeFileSync = function( /*String|Buffer*/ data, /*Object*/ option) {
    Assert.ok(arguments.length == 1 || arguments.length == 2, 'invalid arguments');
	FS.writeFileSync(this._path, data, option);
};

//============================================================

module.exports = PGFile;